﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Reflection;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace BingMapsSample
{
    // Find a Location by Query
    // http://msdn.microsoft.com/en-us/library/ff701711.aspx
    public class LocationsQueryProvider : QueryProviderBase
    {
        const string BingMapsLocationUriFormat = "http://dev.virtualearth.net/REST/v1/Locations/{1}?key={0}";
        const string BingMapsKey = "AouWdeOWKNv3dVEeZ-FxSkb8qN8x_DymYTN0iBu8dBxNovkgow-eKwxhOm-RphlL";

        public override TResult Execute<TResult>(Expression expression)
        {
            var methodCall = expression as MethodCallExpression;
            if (methodCall == null || methodCall.Arguments.Count < 2) throw new InvalidOperationException();

            var u = methodCall.Arguments.OfType<UnaryExpression>().First();
            if (u == null) throw new InvalidOperationException();

            var operand = u.Operand as Expression<Func<Location, bool>>;
            if (operand == null) throw new InvalidOperationException();

            var body = operand.Body as BinaryExpression;
            if (body == null || body.NodeType != ExpressionType.Equal) throw new InvalidOperationException();

            var property = body.Left as MemberExpression;
            if (property == null || property.Member.Name != "Name") throw new InvalidOperationException();

            var name = Evaluate<string>(body.Right);
            string targetUri = string.Format(BingMapsLocationUriFormat, BingMapsKey, name);

            using (var client = new WebClient())
            {
                var json = client.DownloadString(targetUri);

                var obj = JObject.Parse(json);
                var sets = obj["resourceSets"][0]["resources"];

                return (TResult)sets
                    .Select(s => new Location
                    {
                        Name = s["name"].Value<string>(),
                        Point = s["point"].ToCoordinate(),
                        Address = s["address"].ToObject<Address>(),
                    });
            }
        }

        static T Evaluate<T>(Expression expression)
        {
            var constant = expression as ConstantExpression;
            if (constant != null)
            {
                return (T)constant.Value;
            }

            var member = expression as MemberExpression;
            if (member != null)
            {
                var field = member.Member as FieldInfo;
                if (field != null)
                {
                    var fc = (ConstantExpression)member.Expression;
                    return (T)field.GetValue(fc.Value);
                }

                var property = member.Member as PropertyInfo;
                if (property != null)
                {
                    var obj = Evaluate<object>(member.Expression);
                    return (T)property.GetValue(obj, null);
                }
            }

            throw new InvalidOperationException();
        }
    }

    static class LocationsHelper
    {
        public static Coordinate ToCoordinate(this JToken point)
        {
            return new Coordinate
            {
                Latitude = point["coordinates"][0].Value<double>(),
                Longitude = point["coordinates"][1].Value<double>(),
            };
        }
    }

    [DebuggerDisplay(@"\{{Name}\}")]
    public class Location
    {
        public string Name { get; set; }
        public Coordinate Point { get; set; }
        public Address Address { get; set; }
    }

    public class Address
    {
        public string AdminDistrict { get; set; }
        public string CountryRegion { get; set; }
        public string FormattedAddress { get; set; }
        public string Locality { get; set; }

        public override string ToString()
        {
            return string.Format("{0}, {1}, {2}", Locality, AdminDistrict, CountryRegion);
        }
    }

    public class Coordinate
    {
        public double Latitude { get; set; }
        public double Longitude { get; set; }

        public override string ToString()
        {
            return string.Format("({0}, {1})", Latitude, Longitude);
        }
    }
}
